#include <cassert>
#include <cstdlib>
#include <cstring>

#include <fstream>

#include "shaderprogram.h"

using namespace std;

GLint compileShader(char const * filePath, GLuint shaderId, string & log)
{
    // Open file, get length
    ifstream file(filePath, ios::binary);
    file.seekg(0, ios::end);
    size_t fileLen = file.tellg();
    file.seekg(0, ios::beg);
    
    // Compile source file
    char * fileContent = (char*)malloc(fileLen + 1);
    memset(fileContent, 0, fileLen + 1);
    file.read(fileContent, fileLen);
    GLchar const * constContent = fileContent;
    glShaderSource(shaderId, 1, &constContent, NULL);
    glCompileShader(shaderId);
    free(fileContent);
    
    // Get compile status
    GLint status = GL_FALSE;
    glGetShaderiv(shaderId, GL_COMPILE_STATUS, &status);
    
    // Get compiling log
    GLint logLen = 0;
    glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logLen);
    
    char * logContent = (char*)malloc(logLen);
    glGetShaderInfoLog(shaderId, logLen, &logLen, logContent);
    log.assign(logContent, logContent + logLen);
    free(logContent);
    
    return status;
}

ShaderProgram::ShaderProgram(const char *vShaderFile, const char *fShaderFile, GLEWContext const* context):
eqtest::GLEWContextHolder(context)
{
    programId = vShaderId = fShaderId = 0;
    // Preset separate status to false
    linkStatus = vShaderStatus = fShaderStatus = GL_FALSE;
    
    // preset to true
    overallStatus = true;
    
    programId = glCreateProgram();
    
    // Load vertex shader
    if(vShaderFile != NULL)
    {
        vShaderId = glCreateShader(GL_VERTEX_SHADER);
        vShaderStatus = compileShader(vShaderFile, vShaderId, strVertexLog);
        if(vShaderStatus == GL_TRUE)
            glAttachShader(programId, vShaderId);
        else
            overallStatus = false;
    }
    // Load fragment shader
    if(fShaderFile != NULL)
    {
        fShaderId = glCreateShader(GL_FRAGMENT_SHADER);
        fShaderStatus = compileShader(fShaderFile, fShaderId, strFragmentLog);
        if(fShaderStatus == GL_TRUE)
            glAttachShader(programId, fShaderId);
        else
            overallStatus = false;
    }
    // Link if any of the shaders successfully compiles
    if(vShaderStatus == GL_TRUE || fShaderStatus == GL_TRUE)
    {
        glLinkProgram(programId);
        
        glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
        
        GLint logLen = 0;
        glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logLen);
        
        char * logContent = (char*)malloc(logLen);
        glGetProgramInfoLog(programId, logLen, &logLen, logContent);
        strLinkLog.assign(logContent, logContent + logLen);
        free(logContent);
        
        if(linkStatus != GL_TRUE)
            overallStatus = false;
    }
    else
        overallStatus = false;
}

ShaderProgram::~ShaderProgram()
{
    glDeleteShader(vShaderId);
    glDeleteShader(fShaderId);
    glDeleteProgram(programId);
}

void ShaderProgram::bind()
{
    glUseProgram(programId);
}

void ShaderProgram::release()
{
    glUseProgram(0);
}

GLint ShaderProgram::uniform(const char *name)
{
    GLint location = glGetUniformLocation(programId, (GLchar*)name);
    #if 0
    if(location < 0)
    {
        fprintf(stderr, "Name \"%s\" doesn't exist.\n", name);
        abort();
    }
    #endif
    return location;
}
